package com.geek.bulk.interceptor;

import org.apache.commons.lang3.StringUtils;
import org.eclipse.microprofile.faulttolerance.ExecutionContext;
import org.eclipse.microprofile.faulttolerance.Fallback;
import org.eclipse.microprofile.faulttolerance.FallbackHandler;

import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import static com.geek.bulk.util.ClassUtils.getTypes;
import static com.geek.bulk.util.ClassUtils.isDerived;


/**
 * 回退(Fallback)
 */
@Interceptor
public class FallbackInterceptor extends AnnotatedInterceptor<Fallback> {
    @Override
    protected Object execute(InvocationContext context, Fallback fallback) throws Exception {
        try {
            return context.proceed();
        } catch (Exception e) {
            Throwable failure = e instanceof InvocationTargetException ? e.getCause() : e;
            if (!isApplyOn(fallback, failure) || isSkipOn(fallback, failure)) {
                throw new RuntimeException(e);
            }
            //执行fallback进行保底
            return handleFallBack(context, fallback, failure);
        }
    }


    private Object handleFallBack(InvocationContext context, Fallback fallback, Throwable failure) throws Exception {
        String fallbackMethod = fallback.fallbackMethod();
        if (!StringUtils.isEmpty(fallbackMethod)) {

            Class<?> targetClass = context.getMethod().getDeclaringClass();
            Class<?>[] types = getTypes(context.getParameters());
            Method method = targetClass.getMethod(fallbackMethod, types);
            return method.invoke(context.getTarget(), context.getParameters());
        }
        Class<? extends FallbackHandler<?>> value = fallback.value();
        FallbackHandler<?> fallbackHandler = value.newInstance();
        return fallbackHandler.handle(new ExecutionContext() {
            @Override
            public Method getMethod() {
                return context.getMethod();
            }
            @Override
            public Object[] getParameters() {
                return context.getParameters();
            }
            @Override
            public Throwable getFailure() {
                return failure;
            }
        });
    }

    private boolean isApplyOn(Fallback fallback, Throwable e) {
        return isDerived(e.getClass(), fallback.applyOn());
    }

    private boolean isSkipOn(Fallback fallback, Throwable e) {
        return isDerived(e.getClass(), fallback.skipOn());
    }
}
